package blackstar.performance.transform;

import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import java.util.logging.Logger;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;

import blackstar.performance.InvokeDetailPerformance;
import blackstar.performance.InvokePerformance;
import blackstar.performance.PerformanceClassFileTransformer;
import blackstar.performance.PerformanceConfigConstants;
import blackstar.performance.exception.PerformanceException;
import blackstar.performance.logger.RollingFileLogger;
import blackstar.performance.logger.RollingFileLoggerFactory;
import blackstar.performance.transform.instrutment.PerformanceClassVisitor;
import blackstar.performance.transform.matcher.PointCutMatcher;

/**
 * @author RayWoo (ayufox@gmail.com)
 * 
 */
public class PerformanceTransformer implements PerformanceClassFileTransformer
{
	private final static Logger LOGGER = Logger
			.getLogger(PerformanceTransformer.class.getSimpleName());
	private PointCutMatcher methodMatcher = null;
	private Properties configs;
	private RollingFileLogger rollingFileLogger;
	private int type = 0;
	private Set<ClassLoader> excludeClassLoaders;
	private Set<String> loadedClassNames = new HashSet<String>();

	public PerformanceTransformer(Properties configs,
			Set<ClassLoader> excludeClassLoaders)
	{
		super();
		this.configs = configs;
		this.excludeClassLoaders = excludeClassLoaders;

		type = PerformanceClassVisitor.TYPE_PERFORMANCE;
		String typeStr = this.configs.getProperty("type");
		boolean logtime = isLogTime();
		int bufferSize = getBufferSize();

		LOGGER.info("Log Buffer Size[" + bufferSize + "]");

		if (typeStr == null || typeStr.length() == 0
				|| "simple".equals(typeStr))
		{
			this.rollingFileLogger = RollingFileLoggerFactory
					.create(
							configs
									.getProperty(PerformanceConfigConstants.PERFORMANCE_LOG_PATH),
							"simple", bufferSize);

			InvokePerformance.init(this.rollingFileLogger, bufferSize, logtime);
			type = PerformanceClassVisitor.TYPE_PERFORMANCE;
		} else if ("detail".equals(typeStr))
		{
			this.rollingFileLogger = RollingFileLoggerFactory
					.create(
							configs
									.getProperty(PerformanceConfigConstants.PERFORMANCE_LOG_PATH),
							"detail", bufferSize);

			InvokeDetailPerformance.init(this.rollingFileLogger, bufferSize, logtime);
			type = PerformanceClassVisitor.TYPE_DETAIL_PERFORMANCE;
		} else
		{
			throw new PerformanceException("type [" + typeStr + "] not found!");
		}
	}

	public void clearPreloadedClassNames()
	{
		this.loadedClassNames.clear();
	}

	public boolean reloadWeavingConfig()
	{
		PerformanceConfiger performanceConfiger = new PerformanceConfiger();
		PointCutMatcher matcher = performanceConfiger
				.parsePointCutMatcher(configs);
		if (matcher == null)
		{
			return false;
		} else
		{
			this.methodMatcher = matcher;
			return true;
		}
	}

	public boolean testWeaving(Class clazz)
	{
		String className = clazz.getName();
		if (this.loadedClassNames.contains(className))
		{
			return true;
		}
		
		String pkgName = "";
		String simpleClassName = className;
		int index = className.lastIndexOf('.');
		if (index > 0)
		{
			pkgName = className.substring(0, index);
			simpleClassName = className.substring(index + 1);
		}
		
		String matchPackage = this.methodMatcher.matchPackage(pkgName);
		if (matchPackage == null)
		{
			return false;
		}

		return this.methodMatcher.isClassMatch(simpleClassName);
	}

	private int getBufferSize()
	{
		String bufferSize = this.configs.getProperty("buffer");
		if (bufferSize == null || bufferSize.length() == 0)
		{
			return RollingFileLogger.DEFAULT_BUFFER_SIZE;
		}

		if (bufferSize.endsWith("K") || bufferSize.endsWith("k"))
		{
			return (int) Float.parseFloat(bufferSize.substring(0, bufferSize
					.length() - 1)) * 1024;
		}

		if (bufferSize.endsWith("M") || bufferSize.endsWith("m"))
		{
			return (int) Float.parseFloat(bufferSize.substring(0, bufferSize
					.length() - 1)) * 1024 * 1024;
		}

		return Integer.parseInt(bufferSize);
	}

	private boolean isLogTime()
	{
		String logtimeStr = this.configs.getProperty("logtime");
		return ("true".equalsIgnoreCase(logtimeStr));
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.instrument.ClassFileTransformer#transform(java.lang.ClassLoader,
	 *      java.lang.String, java.lang.Class, java.security.ProtectionDomain,
	 *      byte[])
	 */
	public byte[] transform(ClassLoader loader, String className,
			Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
			byte[] classfileBuffer) throws IllegalClassFormatException
	{
		if (loader == null)
		{
			return null;
		}

		if (excludeClassLoaders.contains(loader))
		{
			return null;
		}

		if (this.methodMatcher == null)
		{
			return null;
		}

		String matchClassName = className.replace('/', '.');

		int index = matchClassName.lastIndexOf('.');
		String packageName = null;
		String simpleClassname = null;
		if (index > 0)
		{
			packageName = matchClassName.substring(0, index);
			simpleClassname = matchClassName.substring(index + 1,
					matchClassName.length());
		} else
		{
			packageName = "";
			simpleClassname = matchClassName;
		}

		String matchPackage = this.methodMatcher.matchPackage(packageName);
		if (matchPackage == null)
		{
			return null;
		}

		if (!this.methodMatcher.isClassMatch(simpleClassname))
		{
			return null;
		}

		String recordClassName = simpleClassname;
		if (packageName.length() > 0)
		{
			if (matchPackage.length() > 0)
			{
				recordClassName = packageName
						.substring(matchPackage.length() + 1)
						+ "." + recordClassName;
			} else
			{
				recordClassName = packageName + "." + recordClassName;
			}
		}

		ClassReader cr = new ClassReader(classfileBuffer);
		ClassWriter cw = new ClassWriter(cr, 0);

		PerformanceClassVisitor ca = new PerformanceClassVisitor(cw,
				this.methodMatcher, type, recordClassName);
		cr.accept(ca, ClassReader.EXPAND_FRAMES);

		if (ca.isChange())
		{
			LOGGER.info("Weaving :" + matchClassName);

			loadedClassNames.add(matchClassName);

			return cw.toByteArray();
		} else
		{
			return null;
		}
	}
}